package org.fujene.memoryblock.optimizer;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;

import javassist.NotFoundException;

import org.fujene.communicate.hash.HashCompareTool;
import org.fujene.communicate.hash.VirtualContentHash;
import org.fujene.communicate.lock.IndexableSwitch;
import org.fujene.constants.Constants;
import org.fujene.diskblock.AppendixDiskBlock;
import org.fujene.diskblock.repli.ReplicateContentDiskBlock;
import org.fujene.diskblock.repli.ReplicateInvertIndexDiskBlock;
import org.fujene.logging.LogParser;
import org.fujene.memcon.ReplicateIndexMemory;
import org.fujene.memoryblock.AppendixBlock;
import org.fujene.memoryblock.repli.ReplicateContentBlock;
import org.fujene.memoryblock.repli.ReplicateInvertIndexBlock;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.IndexQueue;
import org.fujene.structure.InvertIndexData;
import org.fujene.structure.LookupPointer;

/*
 * This optimizer's rule:
 * 1. It targets to all blocks on memory and disk.
 * 2. Number of file on disk shrinks to specified number.
 */
public final class StandardOptimizer extends Optimizer{
	private static final int INVSIZE = Constants.BYTE_GLOBALPTR + 5 + Constants.BYTE_TERMFREQ;

	private ReplicateIndexMemory indexMem;
	private HashSet<Long> deletedIDs;
	private HashSet<LookupPointer> deletedAppendices;
	private HashMap<LookupPointer, LookupPointer> contentTransponder;
	private HashMap<LookupPointer, LookupPointer> invIndexTransponder;
	private HashMap<LookupPointer, LookupPointer> termTransponder;
	private HashMap<LookupPointer, LookupPointer> appTransponder;
	private ArrayList<ReplicateContentDiskBlock> contentDisks;
	// private ArrayList<AppendixDiskBlock> appendixDisks;
	private ArrayList<ReplicateInvertIndexDiskBlock> invIndexDisks;
	private int newFileNumber;
	private String directory;

	public StandardOptimizer(ReplicateIndexMemory indexMem, int newFileNum, String directory){
		this.indexMem = indexMem;
		this.termHashFrom = 0L;
		this.termHashTo = -1L; // 0xFFFF...FFFF
		this.contentHashFrom = 0L;
		this.contentHashTo = -1L; // 0xFFFF...FFFF
		this.deletedIDs = new HashSet<Long>();
		this.deletedAppendices = new HashSet<LookupPointer>();
		this.invIndexTransponder = new HashMap<LookupPointer, LookupPointer>();
		this.contentTransponder = new HashMap<LookupPointer, LookupPointer>();
		this.termTransponder = new HashMap<LookupPointer, LookupPointer>();
		this.appTransponder = new HashMap<LookupPointer, LookupPointer>();
		this.contentDisks = new ArrayList<ReplicateContentDiskBlock>();
		this.invIndexDisks = new ArrayList<ReplicateInvertIndexDiskBlock>();
		this.newFileNumber = newFileNum;
		this.directory = directory;
	}

	private int assumeNewFileSize(int maxFile){
		int fileNum = indexMem.getFileNum();
		int fileSize = indexMem.getBlockSize();

		return Math.max(fileSize, fileSize * (fileNum / maxFile));
	}

	private void optimizeContentDisk(int newSize){
		ReplicateContentBlock renewBlocks = null;
		ReplicateContentDiskBlock storeDisk = null;
		try{
			int blockIndex = indexMem.getMinimumBlockOnDisk(Constants.MEMORY_TYPE_CONTENTS);
			if(blockIndex == -1) return;
			LookupPointer renewPtr = new LookupPointer(Integer.MAX_VALUE - blockIndex, 0);
			LookupPointer diskPtr = new LookupPointer(blockIndex, 0);
			renewBlocks = new ReplicateContentBlock(newSize, Integer.MAX_VALUE - blockIndex, true);
			while(true){
				IndexQueue queue = indexMem.lookupContentsAll(diskPtr);

				if(diskPtr.point + queue.bytes >= indexMem.getBlockSize() || queue.bytes < 0){
					// delete disk block
					try{
						while(blockIndex > 0)
							blockIndex = indexMem.getNextFileNum(Constants.MEMORY_TYPE_CONTENTS, blockIndex);
						blockIndex *= -1;
					}catch(NotFoundException e){
						break;
					}
					diskPtr.block = blockIndex;
					diskPtr.point = 0;
					queue = indexMem.lookupContentsAll(diskPtr);
				}

				// Copy to new block
				if(HashCompareTool.existsInMyNode(queue.ID)){
					if(renewPtr.point + queue.bytes >= newSize){
						renewBlocks.resetID(Integer.MAX_VALUE - renewBlocks.getID());
						storeDisk = new ReplicateContentDiskBlock(renewBlocks, indexMem.getDirectory());
						storeDisk.save(0, storeDisk.getData().length, directory);
						contentDisks.add(storeDisk);
						renewBlocks = new ReplicateContentBlock(newSize, Integer.MAX_VALUE - blockIndex, true);
						renewPtr.block = Integer.MAX_VALUE - blockIndex;
						renewPtr.point = 0;
					}
					try{
						renewBlocks.setBody(indexMem.lookupRawData(diskPtr, queue.bytes), renewPtr.point);
					}catch(Exception e){
						// e.printStackTrace();
						System.out.println("Error while reading " + diskPtr);
					}

					contentTransponder.put(new LookupPointer(diskPtr), new LookupPointer(Integer.MAX_VALUE
							- renewPtr.block, renewPtr.point));
					// Debug
					// System.out.println("Add: " + diskPtr + " -> " + new
					// LookupPointer(Integer.MAX_VALUE
					// - renewPtr.block, renewPtr.point));
					renewPtr.point += queue.bytes;
				}else{
					// add to deleted list
					LookupPointer[] deleteApp = indexMem.lookupAppendixPointer(diskPtr);
					for(int j = 0; j < deleteApp.length; j++)
						deletedAppendices.add(deleteApp[j]);
					deletedIDs.add(queue.ID);
					contentTransponder.put(new LookupPointer(diskPtr), new LookupPointer(queue.next));
					// Debug
					// System.out.println("Delete: " + diskPtr + " -> " +
					// queue.next);
				}

				diskPtr.point += queue.bytes;
			}
		}catch(Exception ignored){
			ignored.printStackTrace();
		}
	}

	private void reconnectContentDiskPointer(){
		int ptr;
		for(ReplicateContentDiskBlock diskBlock: contentDisks){
			ptr = 0;
			ReplicateContentBlock memoryBlock = new ReplicateContentBlock(diskBlock.getData(),
					diskBlock.getData().length, diskBlock.getID(), true);
			IndexQueue queue = memoryBlock.getContentAll(ptr);

			if(ptr + queue.bytes >= memoryBlock.getBodySize()) continue;

			LookupPointer oldPtr = memoryBlock.getContentAll(ptr).next;
			memoryBlock.setContentPointer(ptr, contentTransponder.get(oldPtr));
			LookupPointer[] appPtrs = memoryBlock.getAppendixPointer(ptr);
			for(int i = 0; i < indexMem.contentInfo().getAppendixNum(); i++)
				memoryBlock.setAppendixPointer(ptr, appTransponder.get(appPtrs[i]), i);

			ptr += queue.bytes;
		}
	}

	private void optimizeContent(){
		/* optimize contents */
		// Check whether contents are in range of hash
		LookupPointer currentPtr = new LookupPointer();
		LookupPointer lastPtr = new LookupPointer();
		LookupPointer newPtr;

		IndexQueue eachContent;
		int counter = 0;

		// check on every type of contents
		for(int i = 0; i < indexMem.contentInfo().getContentNum(); i++){
			counter = 0;
			eachContent = indexMem.lookupContentsAll(currentPtr);

			while(!currentPtr.equals(LookupPointer.EMPTY)){
				if(indexMem.isOnMemory(currentPtr.block)){
					// delete if content's hash range is out
					if(!HashCompareTool.existsInMyNode(eachContent.hash))
						indexMem.deleteContents(currentPtr);

					// reset connection
					if(indexMem.isContentDeleted(currentPtr)){
						// set connection between previous content and next
						indexMem.storeContentNext(lastPtr, eachContent.next);
						LookupPointer[] deleteApp = indexMem.lookupAppendixPointer(currentPtr);
						for(int j = 0; j < deleteApp.length; j++)
							deletedAppendices.add(deleteApp[j]);
						deletedIDs.add(eachContent.ID);
					}
				}

				lastPtr.block = currentPtr.block;
				lastPtr.point = currentPtr.point;
				currentPtr = eachContent.next;
				counter++;
				if(currentPtr.equals(LookupPointer.EMPTY)) break;
				eachContent = indexMem.lookupContentsAll(currentPtr);
			}

			// clear memory
			newPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_CONTENTS, true), 0);
			indexMem.resetBlockVacancy(newPtr.block);
			currentPtr = new LookupPointer(newPtr);

			eachContent = indexMem.lookupContentsAll(currentPtr);
			for(int j = 0; j < counter; j++){
				if(eachContent == null) break;
				if(eachContent.value == null){
					// read again
					currentPtr.block = indexMem
							.getNextBlock(Constants.MEMORY_TYPE_CONTENTS, currentPtr.block);
					if(currentPtr.block == -1) break;
					currentPtr.point = 0;
					eachContent = indexMem.lookupContentsAll(currentPtr);
				}

				if(!eachContent.value.equals(Constants.DELETED)){
					if(newPtr.point + eachContent.bytes >= indexMem.getBlockSize()){
						// set another block
						newPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_CONTENTS, newPtr.block);
						indexMem.resetBlockVacancy(newPtr.block);
						newPtr.point = 0;
					}
					indexMem.storeRawData(newPtr, indexMem.lookupRawData(currentPtr, eachContent.bytes));
					contentTransponder.put(new LookupPointer(currentPtr), new LookupPointer(newPtr));
					// advance bytes
					newPtr.point += eachContent.bytes;
				}else contentTransponder.put(new LookupPointer(currentPtr), new LookupPointer(
						eachContent.next));

				if(eachContent.ID == -1){
					currentPtr.block = indexMem
							.getNextBlock(Constants.MEMORY_TYPE_CONTENTS, currentPtr.block);
					if(currentPtr.block == -1) break;
					currentPtr.point = 0;
				}else currentPtr.point += eachContent.bytes;
				eachContent = indexMem.lookupContentsAll(currentPtr);
			}
		}
	}

	private void reconnectContentPointer(){
		// update pointers
		LookupPointer currentPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_CONTENTS,
				true), 0);
		IndexQueue eachContent;
		for(int j = 0; j < contentTransponder.size(); j++){
			eachContent = indexMem.lookupContentsAll(currentPtr);
			if(eachContent == null){
				if(currentPtr.block == Integer.MAX_VALUE) break;
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_CONTENTS, currentPtr.block);
				currentPtr.point = 0;
				eachContent = indexMem.lookupContentsAll(currentPtr);
			}
			// Debug
			// System.out.println(eachContent.next + " -> " +
			// contentTransponder.get(eachContent.next));
			indexMem.storeContentNext(currentPtr, (eachContent.next == null ? LookupPointer.EMPTY
					: contentTransponder.get(eachContent.next)));
			if(eachContent.ID == -1){
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_CONTENTS, currentPtr.block);
				currentPtr.point = 0;
			}else currentPtr.point += eachContent.bytes;
		}
	}

	private void reconnectContentSkipPointer(){
		// update skip pointer
		for(int i = 0; i < indexMem.contentInfo().getContentNum(); i++){
			for(int j = 0; j < indexMem.getSkipContentsLength(i); j++)
				indexMem.storeSkipPointer(j, i, (contentTransponder.get(indexMem.lookupContentsSkipPointer(j,
						i)) != null ? contentTransponder.get(indexMem.lookupContentsSkipPointer(j, i))
						: indexMem.lookupContentsSkipPointer(j, i)), true, Constants.MEMORY_TYPE_SKIPPTRCONT);
		}
	}

	private void optimizeAppendixDisk(int newSize){
		if(indexMem.contentInfo().getAppendixNum() == 0) return;
		AppendixBlock renewBlocks = null;
		try{
			int blockIndex = indexMem.getMinimumBlockOnDisk(Constants.MEMORY_TYPE_APPENDIX);
			LookupPointer renewPtr = new LookupPointer(Integer.MAX_VALUE - blockIndex, 0);
			LookupPointer diskPtr = new LookupPointer(blockIndex, 0);
			renewBlocks = new AppendixBlock(newSize, Integer.MAX_VALUE - blockIndex, true);
			while(true){
				int length = indexMem.getAppendixSize(diskPtr);

				if(diskPtr.point + length >= indexMem.getBlockSize()){
					try{
						while(blockIndex > 0)
							blockIndex = indexMem.getNextFileNum(Constants.MEMORY_TYPE_APPENDIX, blockIndex);
						blockIndex *= -1;
					}catch(NotFoundException e){
						break;
					}
					diskPtr.block = blockIndex;
					diskPtr.point = 0;
				}

				// Copy to new block
				if(!deletedAppendices.contains(diskPtr)){
					if(renewPtr.point + length >= newSize){
						AppendixDiskBlock storeDisk = new AppendixDiskBlock(renewBlocks,
								indexMem.getDirectory());
						storeDisk.save(0, storeDisk.getData().length, directory);
						renewBlocks = new AppendixBlock(newSize, Integer.MAX_VALUE - blockIndex, true);
						renewPtr.block = Integer.MAX_VALUE - blockIndex;
						renewPtr.point = 0;
					}
					indexMem.storeRawData(renewPtr, indexMem.lookupRawData(renewPtr, length));
					appTransponder.put(new LookupPointer(diskPtr), new LookupPointer(renewPtr));
					renewPtr.point += length;
				}

				diskPtr.point += length;
			}
		}catch(Exception ignored){
			ignored.printStackTrace();
		}
	}

	private void optimizeAppendix(){
		if(indexMem.contentInfo().getAppendixNum() == 0) return;
		/* optimize appendix zone */
		LookupPointer currentPtr = new LookupPointer();
		LookupPointer newPtr;

		for(int i = 0; i < indexMem.contentInfo().getAppendixNum(); i++){
			currentPtr.block = indexMem.getMinimumBlock(Constants.MEMORY_TYPE_APPENDIX, true);
			currentPtr.point = 0;
			indexMem.resetBlockVacancy(currentPtr.block);
			newPtr = new LookupPointer(currentPtr);

			int appSize;
			while((appSize = indexMem.getAppendixSize(currentPtr)) > 0){
				if(!deletedAppendices.contains(currentPtr)){
					indexMem.storeRawData(newPtr, indexMem.lookupRawData(currentPtr, appSize));
					appTransponder.put(new LookupPointer(currentPtr), new LookupPointer(newPtr));
					newPtr.point += appSize;
					if(newPtr.point >= indexMem.getBlockSize()){
						newPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_APPENDIX, newPtr.block);
						newPtr.point = 0;
					}
				}

				currentPtr.point += appSize;
				if(currentPtr.point >= indexMem.getBlockSize()){
					currentPtr.block = indexMem
							.getNextBlock(Constants.MEMORY_TYPE_APPENDIX, currentPtr.block);
					currentPtr.point = 0;
				}
			}
		}

		// reset pointer from content to appendix
		for(int i = 0; i < indexMem.contentInfo().getContentNum(); i++){
			currentPtr = indexMem.lookupContentsSkipPointer(0, i);
			while(!currentPtr.equals(LookupPointer.EMPTY)){
				LookupPointer[] appendicesPtr = indexMem.lookupAppendixPointer(currentPtr);
				for(int j = 0; j < appendicesPtr.length; j++)
					appendicesPtr[j] = appTransponder.get(appendicesPtr[j]);

				indexMem.storeContentToAppPtr(appendicesPtr, currentPtr);
				currentPtr = indexMem.lookupContentsAll(currentPtr).next;
			}
		}
	}

	private int liveTerm = 0;

	private HashMap<LookupPointer, Long> optimizeDictionary(){
		HashMap<LookupPointer, Long> firstIDs = new HashMap<LookupPointer, Long>();
		ArrayList<LookupPointer> deletedPtrs = new ArrayList<LookupPointer>();
		LookupPointer currentPtr = new LookupPointer();
		LookupPointer newPtr;
		int counter = 0;

		ExtendTerm term;

		currentPtr = indexMem.lookupSkipPointer(0);
		while(currentPtr != null && !currentPtr.equals(LookupPointer.EMPTY)){
			term = indexMem.lookupDictionary(currentPtr, 0);
			if(!HashCompareTool.existsInMyNode(VirtualContentHash.getTermHash(term.term)))
				deletedPtrs.add(currentPtr);
			currentPtr = term.next;
			counter++;
		}

		currentPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_DICT, true), 0);
		indexMem.resetBlockVacancy(currentPtr.block);
		newPtr = null;
		term = indexMem.lookupDictionary(currentPtr, 0);
		for(int j = 0; j < counter && term != null; j++){
			// store top of invert index, that is to delete it
			for(int k = 0; k < term.invIndex.length; k++)
				firstIDs.put(term.invIndex[k], term.firstIDs[k]);
			if(!deletedPtrs.contains(currentPtr)){
				liveTerm++;
				try{
					newPtr = indexMem.storeDictionary(term.term, term.next);
					for(int k = 0; k < term.invIndex.length; k++)
						indexMem.storeDictionaryToInvIndex(newPtr, term.invIndex[k], term.firstIDs[k], k);
				}catch(UnsupportedEncodingException e){}
				termTransponder.put(new LookupPointer(currentPtr), new LookupPointer(newPtr));
				currentPtr.point += term.bytes;
				if(j == counter - 1) break;
				if((term = indexMem.lookupDictionary(currentPtr, 0)) == null){
					currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_DICT, currentPtr.block);
					if(currentPtr.block == Integer.MAX_VALUE) break;
					indexMem.resetBlockVacancy(newPtr.block);
					currentPtr.point = 0;
					term = indexMem.lookupDictionary(currentPtr, 0);
				}else newPtr.point += term.bytes;
			}else termTransponder.put(new LookupPointer(currentPtr), new LookupPointer(term.next));

			if(currentPtr.point + term.createByteArray().length >= indexMem.getBlockSize()){
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_DICT, currentPtr.block);
				if(currentPtr.block == Integer.MAX_VALUE) break;
				currentPtr.point = 0;
				term = indexMem.lookupDictionary(currentPtr, 0);
			}
		}

		// change connected pointers
		currentPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_DICT, true), 0);
		term = indexMem.lookupDictionary(currentPtr, 0);
		for(int j = 0; j < liveTerm; j++){
			indexMem.storeDictionaryPointer(currentPtr, termTransponder.get(term.next));
			currentPtr.point += term.bytes;
			if(j == liveTerm - 1) break;
			if((term = indexMem.lookupDictionary(currentPtr, 0)) == null){
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_DICT, currentPtr.block);
				currentPtr.point = 0;
				term = indexMem.lookupDictionary(currentPtr, 0);
			}
		}

		return firstIDs;
	}

	private void optimizeInvertIndexDisk(int newSize){
		ReplicateInvertIndexBlock renewBlocks = null;
		try{
			int blockIndex = indexMem.getMinimumBlockOnDisk(Constants.MEMORY_TYPE_INVINDEX);
			if(blockIndex == -1) return;
			LookupPointer renewPtr = new LookupPointer(Integer.MAX_VALUE - blockIndex, 0);
			LookupPointer diskPtr = new LookupPointer(blockIndex, 0);
			renewBlocks = new ReplicateInvertIndexBlock(newSize, Integer.MAX_VALUE - blockIndex, true);
			while(true){
				InvertIndexData invdata = indexMem.lookupInvertIndex(diskPtr);

				// next block
				if(diskPtr.point + INVSIZE >= indexMem.getBlockSize()){
					try{
						while(blockIndex > 0)
							blockIndex = indexMem.getNextFileNum(Constants.MEMORY_TYPE_INVINDEX, blockIndex);
						blockIndex *= -1;
					}catch(NotFoundException e){
						// end optimizing if all block traverses
						break;
					}
					diskPtr.block = blockIndex;
					diskPtr.point = 0;
				}

				if(!deletedIDs.contains(Long.valueOf(invdata.getID()))){
					if(renewPtr.point + INVSIZE >= newSize){
						ReplicateInvertIndexDiskBlock storeDisk = new ReplicateInvertIndexDiskBlock(
								renewBlocks, indexMem.getDirectory());
						storeDisk.save(0, storeDisk.getData().length, directory);
						invIndexDisks.add(storeDisk);
						renewBlocks = new ReplicateInvertIndexBlock(newSize, Integer.MAX_VALUE - blockIndex,
								true);
						renewPtr.block = Integer.MAX_VALUE - blockIndex;
						renewPtr.point = 0;
					}
					indexMem.storeRawData(renewPtr, indexMem.lookupRawData(renewPtr, INVSIZE));
					renewPtr.point += INVSIZE;
				}

				diskPtr.point += INVSIZE;
			}
		}catch(Exception ignored){
			ignored.printStackTrace();
		}
	}

	private void optimizeInvertIndex(HashMap<LookupPointer, Long> firstIDs){
		/* optimize invert index */
		HashSet<LookupPointer> deletedInvIndex = new HashSet<LookupPointer>();
		LookupPointer currentPtr = new LookupPointer();
		LookupPointer newPtr;
		long tempID;
		long lastDiff = 0;

		for(Entry<LookupPointer, Long> eachList: firstIDs.entrySet()){
			currentPtr = eachList.getKey();
			while(currentPtr != null){
				InvertIndexData invData = indexMem.lookupInvertIndex(currentPtr);
				tempID = eachList.getValue().longValue() - lastDiff;
				lastDiff += invData.getID();

				// get deletion
				if(deletedIDs.contains(Long.valueOf(tempID)))
					deletedInvIndex.add(new LookupPointer(currentPtr));
				currentPtr = invData.getPrevPtr();
			}
		}

		// clear memory
		currentPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_INVINDEX, true), 0);
		newPtr = new LookupPointer(currentPtr);
		indexMem.resetBlockVacancy(newPtr.block);

		InvertIndexData invData = indexMem.lookupInvertIndex(currentPtr);
		while(invData != null){
			if(!deletedInvIndex.contains(currentPtr)){
				indexMem.storeRawData(newPtr, indexMem.lookupRawData(currentPtr, INVSIZE));
				invIndexTransponder.put(new LookupPointer(currentPtr), new LookupPointer(newPtr));
				newPtr.point += INVSIZE;
				if(newPtr.point + INVSIZE >= indexMem.getBlockSize()){
					newPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_INVINDEX, newPtr.block);
					newPtr.point = 0;
				}
			}else invIndexTransponder.put(
					new LookupPointer(currentPtr),
					new LookupPointer((invData.getPrevPtr() == null ? LookupPointer.EMPTY : invData
							.getPrevPtr())));

			currentPtr.point += INVSIZE;
			if(currentPtr.point + INVSIZE >= indexMem.getBlockSize()){
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_INVINDEX, currentPtr.block);
				currentPtr.point = 0;
			}

			invData = indexMem.lookupInvertIndex(currentPtr);
		}

	}

	private void reconnectInvertIndexPointers(){
		int ptr;
		for(ReplicateInvertIndexDiskBlock diskBlock: invIndexDisks){
			ptr = 0;
			ReplicateInvertIndexBlock memoryBlock = new ReplicateInvertIndexBlock(diskBlock.getData(),
					diskBlock.getData().length, diskBlock.getID(), true);
			InvertIndexData data = memoryBlock.getInvertIndex(ptr);

			if(ptr + INVSIZE >= memoryBlock.getBodySize()) continue;
			memoryBlock.resetPointer(ptr, data.getPrevPtr());
			ptr += INVSIZE;
		}
	}

	private void optimizeDictionaryPointers(){
		// change connectors to invert index
		// System.out.println(currentPtr);
		LookupPointer currentPtr = new LookupPointer(indexMem.getMinimumBlock(Constants.MEMORY_TYPE_DICT,
				true), 0);
		ExtendTerm term = indexMem.lookupDictionary(currentPtr, 0);
		for(int j = 0; j < liveTerm; j++){
			for(int i = 0; i < term.firstIDs.length; i++){
				// System.out.println(term.invIndex[i] + " -> " +
				// invIndexTransponder.get(term.invIndex[i]));
				indexMem.storeDictionaryToInvIndex(currentPtr,
						(invIndexTransponder.get(term.invIndex[i]) == null ? LookupPointer.EMPTY
								: invIndexTransponder.get(term.invIndex[i])), term.firstIDs[i], i);
			}
			if(j == liveTerm - 1) break;
			currentPtr.point += term.bytes;
			// System.out.println(currentPtr);
			if((term = indexMem.lookupDictionary(currentPtr, 0)) == null){
				currentPtr.block = indexMem.getNextBlock(Constants.MEMORY_TYPE_DICT, currentPtr.block);
				currentPtr.point = 0;
				term = indexMem.lookupDictionary(currentPtr, 0);
			}
		}
	}

	// update skip pointers
	private void reconnectSkipPointer(){
		int skipLength = indexMem.getSkipPointerLength();
		for(int i = 0; i < skipLength; i++)
			indexMem.storeSkipPointer(i, 0, termTransponder.get(indexMem.lookupSkipPointer(i)), true,
					Constants.MEMORY_TYPE_SKIPPTR);
	}

	public void optimize(){
		// Lock myself
		IndexableSwitch.lock();
		long time = 0;
		long total = 0;

		int newSize = assumeNewFileSize(newFileNumber);

		/* optimize contents zone */
		total = time = LogParser.initTime();
		optimizeContent();
		time = LogParser.timerFine("### Optimize content", time);
		optimizeContentDisk(newSize);
		time = LogParser.timerFine("### Optimize content in disk", time);
		/* optimize appendix zone */
		optimizeAppendix();
		time = LogParser.timerFine("### Optimize appendix", time);
		optimizeAppendixDisk(newSize);
		time = LogParser.timerFine("### Optimize appendix in disk", time);
		/* Replace connections */
		reconnectContentPointer();
		time = LogParser.timerFine("### Reconnect content pointer", time);
		reconnectContentDiskPointer();
		time = LogParser.timerFine("### Reconnect content pointer in disk", time);
		reconnectContentSkipPointer();
		time = LogParser.timerFine("### Reconnect skip pointer to content space", time);

		// put away list
		contentTransponder = null;
		appTransponder = null;
		contentDisks = null;
		deletedAppendices = null;

		/* optimize dictionary & skip pointer */
		HashMap<LookupPointer, Long> firstIDs = optimizeDictionary();
		time = LogParser.timerFine("### Optimize dictionary", time);
		/* optimize invert index */
		optimizeInvertIndex(firstIDs);
		time = LogParser.timerFine("### Optimize invert index", time);
		optimizeInvertIndexDisk(newSize);
		time = LogParser.timerFine("### Optimize invert index in disk", time);
		reconnectInvertIndexPointers();
		time = LogParser.timerFine("### Reconnect invert indices", time);
		/* change pointers from directory to invert index */
		optimizeDictionaryPointers();
		time = LogParser.timerFine("### Reconnect dictionary pointers", time);
		time = System.nanoTime();
		reconnectSkipPointer();
		time = LogParser.timerFine("### Reconnect skip pointer to dictionary space", time);
		LogParser.finer("Total elapsed time: " + ((System.nanoTime() - total) / 1000000.0) + " ms");

		// restart finally
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}
}
